<?php
/**
 * SimpleCaptcha class
 *
 */
class SimpleCaptcha {
    /** Width of the image */
    public $width = 65;
    /** Height of the image */
    public $height = 25;
    /** Dictionary word file (empty for random text) */
    public $wordsFile = 'words/en.php';
    /**
     * Path for resource files (fonts, words, etc.)
     *
     * "resources" by default. For security reasons, is better move this
     * directory to another location outise the web server
     *
     */
    public $resourcesPath = 'resources';
    /** Min word length (for non-dictionary random text generation) */
    public $minWordLength = 5;
    /**
     * Max word length (for non-dictionary random text generation)
     *
     * Used for dictionary words indicating the word-length
     * for font-size modification purposes
     */
    public $maxWordLength = 5;
    /** Background color in RGB-array */
    public $backgroundColor = [
        250,
        250,
        250
    ];
    /** Foreground colors in RGB-array */
    public $colors = [
        [
            27,
            78,
            181
        ],
        // blue
        [
            22,
            163,
            35
        ],
        // green
        [
            214,
            36,
            7
        ],
        // red
    ];
    /** Shadow color in RGB-array or null */
    public $shadowColor = null; //array(0, 0, 0);
    /** Horizontal line through the text */
    public $lineWidth = 0;
    /**
     * Font configuration
     *
     * - font: TTF file
     * - spacing: relative pixel space between character
     * - minSize: min font size
     * - maxSize: max font size
     */
    public $fonts = [
        /*'Antykwa'  => array(
            'spacing' => -1,
            'minSize' => 27,
            'maxSize' => 30,
            'font'    => 'AntykwaBold.ttf'
        ),
        'DingDong' => array(
            'spacing' => 1,
            'minSize' => 24,
            'maxSize' => 30,
            'font'    => 'Ding-DongDaddyO.ttf'
        ),
        'Duality'  => array(
            'spacing' => 1,
            'minSize' => 30,
            'maxSize' => 38,
            'font'    => 'Duality.ttf'
        ),
        'Heineken' => array(
            'spacing' => 1,
            'minSize' => 24,
            'maxSize' => 30,
            'font'    => 'Heineken.ttf'
        ),'StayPuft' => array(
            'spacing' => -1,
            'minSize' => 28,
            'maxSize' => 30,
            'font'    => 'StayPuft.ttf'
        ),
        'Antykwa'  => [
            'spacing' => 1,
            'minSize' => 27,
            'maxSize' => 27,
            'font'    => 'AntykwaBold.ttf'
        ],
        'Jura'     => [
            'spacing' => 3,
            'minSize' => 27,
            'maxSize' => 27,
            'font'    => 'Jura.ttf'
        ],
        'Times'    => [
            'spacing' => 1,
            'minSize' => 27,
            'maxSize' => 27,
            'font'    => 'TimesNewRomanBold.ttf'
        ],
        'VeraSans' => [
            'spacing' => 1,
            'minSize' => 27,
            'maxSize' => 27,
            'font'    => 'VeraSansBold.ttf'
        ],*/
        'VeraSans' => [
            'spacing' => 1,
            'minSize' => 27,
            'maxSize' => 27,
            'font'    => 'VeraSansBold.ttf'
        ],
    ];
    /** Wave configuracion in X and Y axes */
    public $Yperiod = 20;
    public $Yamplitude = 2;
    public $Xperiod = 11;
    public $Xamplitude = 5;
    /** letter rotation clockwise */
    public $maxRotation = 4;
    /**
     * Internal image size factor (for better image quality)
     * 1: low, 2: medium, 3: high
     */
    public $scale = 3;
    /**
     * Blur effect for better image quality (but slower image processing).
     * Better image results with scale=3
     */
    public $blur = true;
    /** Debug? */
    public $debug = false;
    /** Image format: jpeg or png */
    public $imageFormat = 'png';
    /** GD image */
    public $im;

    public function __construct($config = []) {
        $this->resourcesPath = __DIR__ . __DS__ . $this->resourcesPath;
    }

    public function CreateImage($txt) {
        $ini = microtime(true);
        $this->ImageAllocate();
        if($txt == '') {
            $text = $this->GetCaptchaText();
        } else {
            $text = $txt;
        }
        $fontcfg = $this->fonts[array_rand($this->fonts)];
        $this->WriteText($text, $fontcfg);
        if(!empty($this->lineWidth)) {
            $this->WriteLine();
        }
        $this->WaveImage();
        if($this->blur && function_exists('imagefilter')) {
            imagefilter($this->im, IMG_FILTER_GAUSSIAN_BLUR);
        }
        $this->ReduceImage();
        if($this->debug) {
            imagestring($this->im, 1, 1, $this->height - 8, "$text {$fontcfg['font']} " . round((microtime(true) - $ini) * 1000) . "ms", $this->GdFgColor);
        }
        /** Output */
        $this->WriteImage();
        $this->Cleanup();
    }

    protected function ImageAllocate() {
        // Cleanup
        if(!empty($this->im)) {
            imagedestroy($this->im);
        }
        $this->im = imagecreatetruecolor($this->width * $this->scale, $this->height * $this->scale);
        // Background color
        $this->GdBgColor = imagecolorallocate($this->im, $this->backgroundColor[0], $this->backgroundColor[1], $this->backgroundColor[2]);
        imagefilledrectangle($this->im, 0, 0, $this->width * $this->scale, $this->height * $this->scale, $this->GdBgColor);
        // Foreground color
        $color           = $this->colors[mt_rand(0, sizeof($this->colors) - 1)];
        $this->GdFgColor = imagecolorallocate($this->im, $color[0], $color[1], $color[2]);
        // Shadow color
        if(!empty($this->shadowColor) && is_array($this->shadowColor) && sizeof($this->shadowColor) >= 3) {
            $this->GdShadowColor = imagecolorallocate($this->im, $this->shadowColor[0], $this->shadowColor[1], $this->shadowColor[2]);
        }
    }

    protected function GetCaptchaText() {
        $text = $this->GetDictionaryCaptchaText();
        if(!$text) {
            $text = $this->GetRandomCaptchaText();
        }
        return $text;
    }

    protected function GetRandomCaptchaText($length = null) {
        if(empty($length)) {
            $length = rand($this->minWordLength, $this->maxWordLength);
        }
        $words  = "abcdefghijlmnopqrstvwyz";
        $vocals = "aeiou";
        $text   = "";
        $vocal  = rand(0, 1);
        for($i = 0; $i < $length; $i++) {
            if($vocal) {
                $text .= substr($vocals, mt_rand(0, 4), 1);
            } else {
                $text .= substr($words, mt_rand(0, 22), 1);
            }
            $vocal = !$vocal;
        }
        return $text;
    }

    function GetDictionaryCaptchaText($extended = false) {
        if(empty($this->wordsFile)) {
            return false;
        }
        // Full path of words file
        if(substr($this->wordsFile, 0, 1) == '/') {
            $wordsfile = $this->wordsFile;
        } else {
            $wordsfile = $this->resourcesPath . '/' . $this->wordsFile;
        }
        if(!file_exists($wordsfile)) {
            return false;
        }
        $fp     = fopen($wordsfile, "r");
        $length = strlen(fgets($fp));
        if(!$length) {
            return false;
        }
        $line = rand(1, (filesize($wordsfile) / $length) - 2);
        if(fseek($fp, $length * $line) == -1) {
            return false;
        }
        $text = trim(fgets($fp));
        fclose($fp);
        /** Change ramdom volcals */
        if($extended) {
            $text   = preg_split('//', $text, -1, PREG_SPLIT_NO_EMPTY);
            $vocals = [
                'a',
                'e',
                'i',
                'o',
                'u'
            ];
            foreach($text as $i => $char) {
                if(mt_rand(0, 1) && in_array($char, $vocals)) {
                    $text[$i] = $vocals[mt_rand(0, 4)];
                }
            }
            $text = implode('', $text);
        }
        return $text;
    }

    protected function WriteLine() {
        $x1    = $this->width * $this->scale * .15;
        $x2    = $this->textFinalX;
        $y1    = rand($this->height * $this->scale * .40, $this->height * $this->scale * .65);
        $y2    = rand($this->height * $this->scale * .40, $this->height * $this->scale * .65);
        $width = $this->lineWidth / 2 * $this->scale;
        for($i = $width * -1; $i <= $width; $i++) {
            imageline($this->im, $x1, $y1 + $i, $x2, $y2 + $i, $this->GdFgColor);
        }
    }

    protected function WriteText($text, $fontcfg = []) {
        if(empty($fontcfg)) {
            // Select the font configuration
            $fontcfg = $this->fonts[array_rand($this->fonts)];
        }
        // Full path of font file
        $fontfile = $this->resourcesPath . '/fonts/' . $fontcfg['font'];
        /** Increase font-size for shortest words: 9% for each glyp missing */
        $lettersMissing = $this->maxWordLength - strlen($text);
        $fontSizefactor = 1 + ($lettersMissing * 0.09);
        // Text generation (char by char)
        $x      = 2 * $this->scale;
        $y      = round(($this->height * 27 / 40) * $this->scale);
        $length = strlen($text);
        for($i = 0; $i < $length; $i++) {
            $degree   = rand($this->maxRotation * -1, $this->maxRotation);
            $fontsize = rand(65, 70);//rand(15, 16) * $this->scale * $fontSizefactor;
            $letter   = substr($text, $i, 1);
            if($this->shadowColor) {
                $coords = imagettftext($this->im, $fontsize, $degree, $x + $this->scale, $y + $this->scale, $this->GdShadowColor, $fontfile, $letter);
            }
            $coords = imagettftext($this->im, $fontsize, $degree, $x, $y, $this->GdFgColor, $fontfile, $letter);
            $x += ($coords[2] - $x) + ($fontcfg['spacing'] * $this->scale);
        }
        $this->textFinalX = $x;
    }

    protected function WaveImage() {
        // X-axis wave generation
        $xp = $this->scale * $this->Xperiod * rand(1, 3);
        $k  = rand(0, 100);
        for($i = 0; $i < ($this->width * $this->scale); $i++) {
            imagecopy($this->im, $this->im, $i - 1, sin($k + $i / $xp) * ($this->scale * $this->Xamplitude), $i, 0, 1, $this->height * $this->scale);
        }
        // Y-axis wave generation
        $k  = rand(0, 100);
        $yp = $this->scale * $this->Yperiod * rand(1, 2);
        for($i = 0; $i < ($this->height * $this->scale); $i++) {
            imagecopy($this->im, $this->im, sin($k + $i / $yp) * ($this->scale * $this->Yamplitude), $i - 1, 0, $i, $this->width * $this->scale, 1);
        }
    }

    protected function ReduceImage() {
        $imResampled = imagecreatetruecolor($this->width, $this->height);
        imagecopyresampled($imResampled, $this->im, 0, 0, 0, 0, $this->width, $this->height, $this->width * $this->scale, $this->height * $this->scale);
        imagedestroy($this->im);
        $this->im = $imResampled;
    }

    protected function WriteImage() {
        if($this->imageFormat == 'png' && function_exists('imagepng')) {
            header("Content-type: image/png");
            imagepng($this->im);
        } else {
            header("Content-type: image/jpeg");
            imagejpeg($this->im, null, 80);
        }
    }

    protected function Cleanup() {
        imagedestroy($this->im);
    }
}